import WebpackLicense from '@components/WebpackLicense';
import { ApiMeta } from '@components/ApiMeta';

<WebpackLicense from="https://webpack.docschina.org/api/module-methods/" />

# 模块方法

当使用 Rspack 打包应用程序时，你可以选择多种模块语法风格，包括 [ES modules](https://nodejs.org/api/esm.html#modules-ecmascript-modules) 和 [CommonJS](https://nodejs.org/api/modules.html)。

尽管 Rspack 支持多种模块语法，但我们还是建议尽量使用一致的语法，以此避免一些奇怪的行为和 bug。

事实上，当距离最近的 `package.json` 文件中 ["type"](https://nodejs.org/api/packages.html#type) 字段值为 `"module"` 或 `"commonjs"` 时，Rspack 会对 `.mjs` 文件、`.cjs` 文件或`.js` 文件进行对应的处理：

- 文件后缀为 `.mjs`，或者 `package.json` 中 `"type": "module"` 且后缀为 `.js` 时：
  - 不允许使用 CommonJS，如 `require`，`module.exports` 或 `exports`
  - 引入文件时需指定扩展名，例如，应使用 `import './src/App.mjs'`，而非 `import './src/App'`
- 文件后缀为 `.cjs`，或者 `package.json` 中 `"type": "commonjs"` 且后缀为 `.js` 时：
  - 不允许使用 ESM，如 `import` 和 `export`

## ES modules (推荐)

Rspack 原生支持 ES modules 语法，可以使用静态的 `import`、`export` 和 `import()` 语法。

:::warning
如果使用其他的 ES6+ 特性，你仍然需要引入 SWC 或 Babel 进行转换。
:::

### import

静态 `import` 另一个模块的 `export`。

```js
import MyModule from './my-module.js';
import { NamedExport } from './other-module.js';
```

你还可以 `import` Data URI，这允许你直接在导入语句中嵌入 Base64 编码的 JavaScript 代码：

```js
// 等同于 import 一个包含 `console.log('hello')` 的模块
import 'data:text/javascript;charset=utf-8;base64,Y29uc29sZS5sb2coJ2hlbGxvJyk=';

// 等同于 import 一个包含 `export const number = 42;` 的模块
import { number } from 'data:text/javascript;charset=utf-8;base64,ZXhwb3J0IGNvbnN0IG51bWJlciA9IDQyOw==';
```

### export

将任何内容作为默认导出或具名导出。

```js
// 具名导出
export var Count = 5;
export function Multiply(a, b) {
  return a * b;
}

// 默认导出
export default {
  // Some data...
};
```

### Dynamic import()

```ts
function import(path: string): Promise;
```

动态加载模块，参考 [Dynamic import](/guide/optimization/code-splitting#动态导入dynamic-import) 了解更多。

对 `import()` 的调用被视为分割点，这意味着请求的模块及其子模块被拆分成单独的 chunk。

```js
if (module.hot) {
  import('lodash').then(_ => {
    // 使用 lodash 做些事情...
  });
}
```

:::warning
此功能内部依赖于 `Promise`。如果你在旧版浏览器中使用 `import()`，请记得使用类似于 [core-js](https://github.com/zloirock/core-js)， [es6-promise](https://github.com/stefanpenner/es6-promise) 或 [promise-polyfill](https://github.com/taylorhakes/promise-polyfill) 的 polyfill 来模拟 `Promise`。
:::

#### import() 中的动态表达式

`import()` 无法使用完全动态的导入语句，例如 `import(foo)`。因为 `foo` 可能是系统或项目中任何文件的任何路径。

`import()` 必须至少包含一些关于模块所在位置的信息。可以将打包限制在特定目录或一组文件中，这样当你使用动态表达式时，在 `import()` 调用中可能被请求的每个模块都会被包括进来。

例如，`import(./locale/${language}.json)` 会将 `./locale` 目录中的每个 `.json` 文件都被打包进新的代码块。在运行时，一旦变量 `language` 被计算出来，任何像 `english.json` 或 `german.json` 这样的文件都将可供使用。

```js
// 想象一下，如果我们有一种方法可以从 Cookie 或其他存储中获取语言
const language = detectVisitorLanguage();
import(`./locale/${language}.json`).then(module => {
  // 做一些翻译相关的事情
});
```

#### Magic comments

<ApiMeta specific={['Rspack', 'Webpack']} />

通过向 import 语句添加注释，我们可以实现「指定 chunk 名称」或「设置加载模式」等操作。有关这些魔法注释的完整列表，请参见下面的代码，以及对这些注释功能的解释。

```js
// 单个模块
import(
  /* webpackChunkName: "my-chunk-name" */
  /* webpackMode: "lazy" */
  /* webpackFetchPriority: "high" */
  'module'
);

// 多个可能模块
import(
  /* webpackInclude: /\.json$/ */
  /* webpackExclude: /\.noimport\.json$/ */
  /* webpackChunkName: "my-chunk-name" */
  /* webpackMode: "lazy" */
  /* webpackPrefetch: true */
  /* webpackPreload: true */
  `./locale/${language}`
);
```

##### webpackIgnore

- **类型：** `boolean`

设置为 `true` 时，Rspack 将跳过对该动态导入的静态分析和打包处理。具体表现为：

1. 构建时不会分析该模块的依赖关系
2. 该模块不会被打包为独立的 chunk 文件
3. 导入操作将在运行时由浏览器原生 `import()` 执行

这在需要从外部 CDN 动态加载 ESM 格式的第三方库时特别有用，例如：

```js
import('https://esm.sh/react' /* webpackIgnore: true */).then(React => {
  console.log(React.version); // 19.0.0
});
```

`webpackIgnore` 同样适用于 `new URL()` 语法。默认情况下，Rspack 会解析 `new URL()` 中的模块标识符，并将其引用的模块打包到构建产物中。当你需要 Rspack 跳过某个 `new URL()` 的处理时，可以添加 `webpackIgnore: true` 注释：

```js
// Rspack 会处理这个 URL，将 './index.css' 打包到构建产物中
const url1 = new URL('./index.css', import.meta.url);

// Rspack 会忽略这个 URL，保持原始的模块标识符
const url2 = new URL(/* webpackIgnore: true */ './index.css', import.meta.url);
```

##### webpackMode

- **类型：** `"eager" | "lazy" | "weak" | "lazy-once"`
- **默认值：** `'lazy'`

可以指定以不同的模式解析动态导入。支持以下选项：

- `'lazy'` (默认值)：为每个 `import()` 导入的模块生成一个可延迟加载（lazy-loadable）的 chunk。
- `'lazy-once'`：生成一个可以满足所有 `import()` 调用的单个可延迟加载（lazy-loadable）的 chunk。此 chunk 将在第一次 `import()` 时调用时获取，随后的 `import()` 则使用相同的网络响应。注意，这种模式仅在部分动态语句中有意义，例如 `import("./locales/${language}.json")`，其中可能含有多个被请求的模块路径。
- `'eager'`：不会生成额外的 chunk。所有的模块都被当前的 chunk 引入，并且没有额外的网络请求。但是仍会返回一个 resolved 状态的 Promise。与静态导入相比，在调用 `import()` 完成之前，该模块不会被执行。
- `'weak'`：尝试加载模块，如果该模块函数已经以其他方式加载，（即另一个 chunk 导入过此模块，或包含模块的脚本被加载）。仍会返回 Promise， 但是只有在客户端上已经有该 chunk 时才会成功解析。如果该模块不可用，则返回 rejected 状态的 Promise，且网络请求永远都不会执行。当需要的 chunks 始终在（嵌入在页面中的）初始请求中手动提供，而不是在应用程序导航在最初没有提供的模块导入的情况下触发，这对于通用渲染（SSR）是非常有用的。

##### webpackPrefetch

- **类型：**
  - `number`: 预取优先级
  - `boolean`: `false` 表示不预取, `true` 表示预取并且优先级是 `0`

告诉浏览器将来可能需要该资源来进行某些导航跳转，详情请查看[预取/预载模块](/guide/optimization/code-splitting#prefetchingpreloading-模块)。

##### webpackPreload

- **类型：**
  - `number`: 预载优先级
  - `boolean`: `false` 表示不预载, `true` 表示预载并且优先级是 `0`

告诉浏览器在当前导航期间可能需要该资源，详情请查看[预取/预载模块](/guide/optimization/code-splitting#prefetchingpreloading-模块)。

##### webpackChunkName

- **类型：**: `string`

指定新 chunk 的名称。

##### webpackFetchPriority

<ApiMeta addedVersion="1.0.0" />

- **类型：**: `"low" | "high" | "auto"`

为指定的动态导入设置 [`fetchPriority`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/fetchPriority)。也可以通过使用 `module.parser.javascript.dynamicImportFetchPriority` 选项为所有动态导入设置全局默认值。

##### webpackInclude

<ApiMeta addedVersion="1.0.0" />

- **类型：**: `Regexp`

在导入解析时匹配的正则表达式。只有匹配的模块**才会被打包**。

##### webpackExclude

<ApiMeta addedVersion="1.0.0" />

- **类型：**: `Regexp`

在导入解析时匹配的正则表达式。只有匹配的模块**不会被打包**。

:::info
请注意，`webpackInclude` 和 `webpackExclude` 选项不会影响前缀。例如：`./locale`。
:::

##### webpackExports

<ApiMeta addedVersion="1.0.0" />

- **Type:**: `string | string[]`

使 Rspack 在处理该动态 `import()` 模块时仅打包指定的导出。这样可以降低 chunk 的产物体积。

## CommonJS

Rspack 也支持 `CommonJS` 语法，可以使用 `require` 和 `module.exports` 语法。

### require

以同步的方式引入其他模块。

```js
require(dependency: string);
```

### require.resolve

以同步的方式获取模块的 ID。此方法会将模块打包进最终的 bundle 中，但不会执行模块代码。返回的模块 ID 主要用于与 `require.cache[id]` 或 Rspack 内部的 `__webpack_require__(id)` 一起使用，在大多数应用场景中应避免直接使用此方法。

```js
require.resolve(dependency: string);
```

:::warning
模块 ID 的类型可以是 `number` 或 `string`，具体取决于 `optimization.moduleIds` 配置。
:::

### require.cache

多处引用同一模块，最终只会产生一次模块执行和一次导出。所以，会在运行时（runtime）中会保存一份缓存。删除此缓存，则会产生新的模块执行和新的导出。

```js
var d1 = require('dependency');
require('dependency') === d1;
delete require.cache[require.resolve('dependency')];
require('dependency') !== d1;
```

### require.context

<ApiMeta specific={['Rspack', 'Webpack']} />

`require.context` 允许你动态地 require 一组模块。

你可以在代码中使用 `require.context`，Rspack 将在构建时进行解析并引用匹配的模块。

:::tip
`require.context` 的返回值与 [import.meta.webpackContext](/api/runtime-api/module-variables#importmetawebpackcontext) 相同，我们推荐使用 `import.meta.webpackContext`，它的功能更加强大。
:::

- **类型：**

```ts
function requireContext(
  /**
   * 要搜索的目录
   */
  directory: string,
  /**
   * 是否还搜索其子目录
   * @default true
   */
  includeSubdirs?: boolean,
  /**
   * 匹配文件的正则表达式
   * @default /^\.\/.*$/（匹配任意文件）
   */
  filter?: RegExp,
  /**
   * 模块加载模式
   * @default 'sync'
   */
  mode?: 'sync' | 'eager' | 'weak' | 'lazy' | 'lazy-once',
): Context;
```

- **示例：**

```js
// 创建一个上下文，
// 文件直接来自 test 目录，匹配的文件名以 `.test.js` 结尾。
const context = require.context('./test', false, /\.test\.js$/);
```

```js
// 创建一个上下文，
// 文件来自父文件夹及其所有子级文件夹，匹配的文件名以 `.stories.js` 结尾。
const context = require.context('../', true, /\.stories\.js$/);
```

```js
// 如果 `mode` 被设置为 `lazy`，模块将会被异步加载
const context = require.context('./locales', true, /\.json$/, 'lazy');
```

:::tip
Rspack 在编译时，会通过静态分析来解析 `require.context` 的参数，因此参数必须是[字面量](https://developer.mozilla.org/en-US/docs/Glossary/Literal)。

例如，`filter` 的值不允许传入一个变量，也不允许传入 `new RegExp()` 生成的值，只能是一个正则表达式字面量。
:::

### require.ensure

<ApiMeta specific={['Rspack', 'Webpack']} />

:::tip
`require.ensure()` 基本已经被 `import()` 取代.
:::

将给定 `dependencies` 单独打包，并异步加载。当使用 CommonJS 语法的时候，这是唯一动态加载依赖的方式。同时，这个 API 也可以用于运行时，只有在满足某些条件时才会加载依赖。

:::warning
该特性内部依赖于 [Promise](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise)。 如果你在旧的浏览器上使用 `require.ensure`， 最好加入 polyfill [es6-promise](https://github.com/stefanpenner/es6-promise) 或 [promise-polyfill](https://github.com/taylorhakes/promise-polyfill)。
:::

- **Type:**

```ts
function requireEnsure(
  /**
   * callback 执行需要的模块列表
   */
  dependencies: String[],
  /**
   * 当 dependencies 中的模块都加载完毕时，这个函数就会被执行。
   * require 函数将作为第一个参数传入该函数。
   * 在这个函数中也可以进一步使用 require() 来加载模块。
   */
  callback: function(require),
  /**
   * 当加载依赖失败的时候执行的函数
   */
  errorCallback?: function(error),
  /**
   * 指定由 require.ensure() 生成的 chunk 名称。
   * 通过指定相同的 chunkName 来合并多个 require.ensure() 调用的代码，从而只生成一个 bundle，
   * 从而减少浏览器加载的次数。
   */
  chunkName?: string
): Context;
```

- **Example:**

```ts
var a = require('normal-dep');

if (module.hot) {
  require.ensure(['b'], function (require) {
    var c = require('c');

    // Do something special...
  });
}
```

### require.resolveWeak

<ApiMeta specific={['Rspack', 'Webpack']} />

与 `require.resolve` 类似，但是不会把 module 打包到最终的 bundle 中。是 "弱（weak）" 依赖。

```js
require.resolveWeak(dependency: string);
```

示例：

```js
if (__webpack_modules__[require.resolveWeak('module')]) {
  // 当模块可用时，执行一些操作
}
if (require.cache[require.resolveWeak('module')]) {
  // 在模块加载完成之前，执行一些操作
}

// 你可以像执行其他 require/import 方法一样，
// 执行动态解析上下文 resolves ("context")
const page = 'Foo';
__webpack_modules__[require.resolveWeak(`./page/${page}`)];
```

## Data URI 模块

Rspack 支持使用 `import` 和 `require` 语法导入 Data URI 模块。

- **import**

```js
import DataURI from 'data:text/javascript,export default 42';
```

- **require**

```js
require('data:text/javascript,module.exports = 42');
```

除此之外，还支持了 Base64 编码：

```js
const {
  number,
  fn,
} = require('data:text/javascript;charset=utf-8;base64,ZXhwb3J0IGNvbnN0IG51bWJlciA9IDQyOwpleHBvcnQgZnVuY3Rpb24gZm4oKSB7CiAgcmV0dXJuICJIZWxsbyB3b3JsZCI7Cn0=');
```

::: tip
Data URI 模块可以被用作虚拟模块（Virtual Modules）的实现方式，如：配合 loader 完成运行时动态加载自定义模块。
:::

### 自定义 Rules

Rspack 默认支持这些 [MIME 类型](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Guides/MIME_types) 的 data URI 模块：`application/json`、`text/javascript`、`application/javascript`、`application/node` 和 `application/wasm`。这意味着当你使用这些 MIME 类型创建 data URI 模块时，Rspack 会自动识别他们。

Rspack 内置的 MIME rules 如下：

```js
const defaultMimeRules = [
  {
    mimetype: 'application/node',
    type: 'javascript/auto',
  },
  {
    mimetype: 'application/json',
    type: 'json',
  },
  {
    mimetype: {
      or: ['text/javascript', 'application/javascript'],
    },
    type: 'javascript/esm',
    // ...
  },
  {
    mimetype: 'application/wasm',
    type: 'webassembly/async',
    // ...
  },
];
```

### 自定义 Rules

你可以也使用 [Rule.mimetype](/config/module#rulemimetype) 来扩展 Data URI 模块的匹配规则，例如为 `text/javascript` 添加自定义的 loaders：

```js title="rspack.config.mjs"
export default {
  module: {
    rules: [
      {
        mimetype: 'text/javascript',
        use: [
          // ...
        ],
      },
    ],
  },
};
```
